package subscribe

import (
	"net/http"
	"parking-lot-go-dingding/config"
	"parking-lot-go-dingding/controller"
	"parking-lot-go-dingding/logger"
	"parking-lot-go-dingding/model"
	"parking-lot-go-dingding/model/dingtalk/system"
	"strconv"

	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"

	"crypto/sha1"
	r "math/rand"
	"sort"
	"strings"
	"time"
)

var dingdingSubscribeLogger = logger.NewLogger("dingtalk", "subscribe")

type BizDataItem struct {
	GmtCreate   int64  `json:"gmt_create"`
	BizType     int    `json:"biz_type"`
	OpenCursor  int    `json:"open_cursor"`
	SubscribeID string `json:"subscribe_id"`
	ID          int    `json:"id"`
	GmtModified int64  `json:"gmt_modified"`
	BizID       string `json:"biz_id"`
	BizData     string `json:"biz_data"`
	CorpID      string `json:"corp_id"`
	Status      int    `json:"status"`
}

// EventData 表示整个 JSON 数据中的 eventData 字段
type EventData struct {
	EventType string        `json:"EventType"`
	BizData   []BizDataItem `json:"bizData"`
}

func Subscribe(c *gin.Context) {
	// var err error

	signature := c.Query("msg_signature")
	timestamp := c.Query("timestamp")
	nonce := c.Query("nonce")

	dingdingSubscribeLogger.Info(fmt.Sprintf("入参 signature:%s,timestamp:%s,nonce:%s", signature, timestamp, nonce))

	encrypt := struct {
		Encrypt string `json:"encrypt"`
	}{}

	//接收加密串
	if err := c.ShouldBindJSON(&encrypt); err != nil {
		dingdingSubscribeLogger.Error(fmt.Sprintf("json接收失败:%s", err.Error()))
		return
	}

	jsonStr, _ := json.Marshal(encrypt)
	dingdingSubscribeLogger.Info(string(jsonStr))

	//钉钉参数
	dingdingSection := config.Cfg.Section("DINGDING")
	token := dingdingSection.Key("TOKEN").String()
	aesKey := dingdingSection.Key("AES_KEY").String()
	suiteKey := dingdingSection.Key("CLIENT_ID").String()

	//解密
	var ding = NewDingTalkCrypto(token, aesKey, suiteKey)
	msg, _ := ding.GetEncryptMsg("success")
	c.JSON(http.StatusOK, msg)

	success, err := ding.GetDecryptMsg(signature, timestamp, nonce, encrypt.Encrypt)
	if err != nil {
		dingdingSubscribeLogger.Error(fmt.Sprintf("解密失败:%s", err.Error()))
		return
	}

	fmt.Println(success)

	//判断事件类型
	var eventData EventData
	err = json.Unmarshal([]byte(success), &eventData)
	if err != nil {
		dingdingSubscribeLogger.Error(fmt.Sprintf("解析事件失败:%s", err.Error()))
		return
	}

	for _, v := range eventData.BizData {
		dingdingSubscribeLogger.Info(fmt.Sprintf("类型为:%d", v.BizType))

		switch v.BizType {
		case 2: //更新suiteTicket
			decodeStruct := struct {
				EventID     string `json:"eventId"`
				SyncAction  string `json:"syncAction"`
				SuiteTicket string `json:"suiteTicket"`
				SyncSeq     string `json:"syncSeq"`
			}{}
			err = json.Unmarshal([]byte(v.BizData), &decodeStruct)
			if err != nil {
				dingdingSubscribeLogger.Error(fmt.Sprintf("解析解析suite json失败:%s", err.Error()))
				return
			}

			model.RedisClient.Set("ding_talk_suite_ticket", decodeStruct.SuiteTicket, 0)

			//刷新ticket
			var dingtalkSystemConfigStruct system.DingtalkSystemConfigStruct
			dingtalkSystemConfigStruct.UpdateTicket(v.CorpID, decodeStruct.SuiteTicket)

		case 4: //企业授权

			decodeStruct := struct {
				SyncAction string `json:"syncAction"`
			}{}

			err = json.Unmarshal([]byte(v.BizData), &decodeStruct)
			if err != nil {
				dingdingSubscribeLogger.Error(fmt.Sprintf("解析企业授权失败:%s", err.Error()))
				return
			}

			switch decodeStruct.SyncAction {
			case "org_suite_auth": //授权
				dingdingSubscribeLogger.Info("企业授权")

				decodeStruct := struct {
					AuthCorpInfo struct {
						Corpid   string `json:"corpid"`
						CorpName string `json:"corp_name"`
					} `json:"auth_corp_info"`
					AuthInfo struct {
						Agent []struct {
							AdminList []string `json:"admin_list"`
						} `json:"agent"`
					} `json:"auth_info"`
				}{}
				err = json.Unmarshal([]byte(v.BizData), &decodeStruct)
				if err != nil {
					dingdingSubscribeLogger.Error(fmt.Sprintf("解析授权json失败:%s", err.Error()))
					return
				}

				//添加企业信息
				var dingtalkSystemCorpStruct system.DingtalkSystemCorpStruct
				systemFind, err := dingtalkSystemCorpStruct.First(decodeStruct.AuthCorpInfo.Corpid)
				if err != nil {
					if !errors.Is(err, gorm.ErrRecordNotFound) {
						dingdingSubscribeLogger.Error(fmt.Sprintf("获取企业信息失败:%s", err.Error()))
						return
					}
				}

				//企业信息已存在
				if systemFind.Id != 0 {
					dingdingSubscribeLogger.Error("企业信息已存在")
					return
				}

				if err := dingtalkSystemCorpStruct.Create(decodeStruct.AuthCorpInfo.Corpid, decodeStruct.AuthCorpInfo.CorpName); err != nil {
					dingdingSubscribeLogger.Error(fmt.Sprintf("添加企业失败:%s", err.Error()))
					return
				}

				//添加管理员
				var dingtalkSystemAdminStruct system.DingtalkSystemAdminStruct
				for _, vv := range decodeStruct.AuthInfo.Agent {
					for _, vvv := range vv.AdminList {
						dingtalkSystemAdminStruct.Create(decodeStruct.AuthCorpInfo.Corpid, vvv)
					}
				}
				dingdingSubscribeLogger.Info("添加管理员信息成功")

				//拉取企业组织架构
				if err := department(decodeStruct.AuthCorpInfo.Corpid, 1); err != nil {
					dingdingSubscribeLogger.Error(err.Error())
					return
				}
			case "org_suite_relieve": //取消
				dingdingSubscribeLogger.Info("企业取消授权")

				//删除组织
				var dingtalkSystemCorpStruct system.DingtalkSystemCorpStruct
				dingtalkSystemCorpStruct.Delete(v.CorpID)

				//删除管理员
				var dingtalkSystemAdminStruct system.DingtalkSystemAdminStruct
				dingtalkSystemAdminStruct.Delete(v.CorpID)

				//todo 想想怎么弄这地方 是否信息全删除
			}
		}
	}

}

// 拉取组织架构
func department(corpId string, deptid int) error {
	accessToken, err := controller.AccessToken(corpId)
	if err != nil {
		return errors.New(fmt.Sprintf("获取access_token失败:%s", err.Error()))
	}

	//拉取组织架构
	url := "https://oapi.dingtalk.com/topapi/v2/department/listsub?access_token=" + accessToken
	responseBody, err := controller.PostJsonRequest(url, map[string]interface{}{
		"dept_id": deptid,
	})

	if err != nil {
		return errors.New(fmt.Sprintf("拉取组织架构失败:%s", err.Error()))
	}

	//组织架构结构体
	type departmentStruct struct {
		AutoAddUser     bool   `json:"auto_add_user"`
		CreateDeptGroup bool   `json:"create_dept_group"`
		DeptID          int    `json:"dept_id"`
		Name            string `json:"name"`
		ParentID        int    `json:"parent_id"`
	}

	type responseStruct struct {
		ErrCode   int                `json:"errcode"`
		ErrMsg    string             `json:"errmsg"`
		Results   []departmentStruct `json:"result"`
		RequestId string             `json:"request_id"`
	}

	var response responseStruct
	err = json.Unmarshal(responseBody, &response)
	if err != nil {
		return errors.New(fmt.Sprintf("组织架构json解析失败:%s", err.Error()))
	}

	var dingtalkSystemDepartmentStruct system.DingtalkSystemDepartmentStruct
	for _, v := range response.Results {

		//添加组织架构
		dingtalkSystemDepartmentStruct.Create(system.DingtalkSystemDepartmentStruct{
			CorpId:      corpId,
			ParentId:    v.ParentID,
			DeptId:      v.DeptID,
			DeptName:    v.Name,
			AutoAddUser: controller.BoolToInt(v.AutoAddUser),
		})

		//取部门下所有用户
		userList(corpId, v.DeptID, v.Name, 0)

		//递归取部门
		if v.DeptID != 1 {
			department(corpId, v.DeptID)
		}
	}

	//根部门数据
	userList(corpId, 1, "根部门", 0)

	dingdingSubscribeLogger.Info("添加组织架构成功")

	return nil
}

// 拉取用户信息
func userList(corpId string, deptId int, deptName string, cursor int) error {
	accessToken, err := controller.AccessToken(corpId)
	if err != nil {
		return errors.New(fmt.Sprintf("获取access_token失败:%s", err.Error()))
	}

	//拉取部门人员
	url := "https://oapi.dingtalk.com/topapi/v2/user/list?access_token=" + accessToken
	responseBody, err := controller.PostJsonRequest(url, map[string]interface{}{
		"dept_id": deptId,
		"cursor":  cursor,
		"size":    100,
	})
	if err != nil {
		return errors.New(fmt.Sprintf("获取部门下人员失败:%s", err.Error()))
	}

	//解析返回值
	responseStruct := struct {
		ErrCode int    `json:"errcode"`
		ErrMsg  string `json:"errmsg"`
		Result  struct {
			HasMore    bool `json:"has_more"`
			NextCursor int  `json:"next_cursor"`
			List       []struct {
				Active           bool   `json:"active"`
				Admin            bool   `json:"admin"`
				Avatar           string `json:"avatar"`
				Boss             bool   `json:"boss"`
				DeptIDList       []int  `json:"dept_id_list"`
				DeptOrder        int    `json:"dept_order"`
				ExclusiveAccount bool   `json:"exclusive_account"`
				HideMobile       bool   `json:"hide_mobile"`
				Leader           bool   `json:"leader"`
				Name             string `json:"name"`
				UnionID          string `json:"unionid"`
				UserID           string `json:"userid"`
				Title            string `json:"title"`
			} `json:"list"`
		} `json:"result"`
		RequestId string `json:"request_id"`
	}{}

	err = json.Unmarshal(responseBody, &responseStruct)
	if err != nil {
		return errors.New(fmt.Sprintf("部门下人员json数据解析失败:%s", err.Error()))
	}

	//添加组织下人员
	var dingtalkSystemDepartmentUserStruct system.DingtalkSystemDepartmentUserStruct
	for _, v := range responseStruct.Result.List {
		var deptIDListStrSlice []string
		for _, num := range v.DeptIDList {
			deptIDListStrSlice = append(deptIDListStrSlice, strconv.Itoa(num))
		}
		deptIDListStr := strings.Join(deptIDListStrSlice, ",")

		dingtalkSystemDepartmentUserStruct.Create(system.DingtalkSystemDepartmentUserStruct{
			Active:           controller.BoolToInt(v.Active),
			Admin:            controller.BoolToInt(v.Admin),
			Avatar:           v.Avatar,
			Boss:             controller.BoolToInt(v.Boss),
			DeptIdList:       deptIDListStr,
			DeptOrder:        v.DeptOrder,
			ExclusiveAccount: controller.BoolToInt(v.ExclusiveAccount),
			HideMobile:       controller.BoolToInt(v.HideMobile),
			Leader:           controller.BoolToInt(v.Leader),
			Name:             v.Name,
			Unionid:          v.UnionID,
			Userid:           v.UserID,
			CorpId:           corpId,
			DepartmentId:     deptId,
			DepartmentName:   deptName,
		})
	}

	//还有更多的数据
	if responseStruct.Result.HasMore == true {

		userList(corpId, deptId, deptName, responseStruct.Result.NextCursor)
	}

	dingdingSubscribeLogger.Info("添加人员成功")

	return nil
}

type DingTalkCrypto struct {
	Token          string
	EncodingAESKey string
	SuiteKey       string
	BKey           []byte
	Block          cipher.Block
}

func NewDingTalkCrypto(token, encodingAESKey, suiteKey string) *DingTalkCrypto {
	if len(encodingAESKey) != int(43) {
		panic("不合法的EncodingAESKey")
	}
	bkey, err := base64.StdEncoding.DecodeString(encodingAESKey + "=")
	if err != nil {
		panic(err.Error())
	}
	block, err := aes.NewCipher(bkey)
	if err != nil {
		panic(err.Error())
	}
	c := &DingTalkCrypto{
		Token:          token,
		EncodingAESKey: encodingAESKey,
		SuiteKey:       suiteKey,
		BKey:           bkey,
		Block:          block,
	}
	return c
}

func (c *DingTalkCrypto) GetDecryptMsg(signature, timestamp, nonce, secretMsg string) (string, error) {
	if !c.VerificationSignature(c.Token, timestamp, nonce, secretMsg, signature) {
		return "", errors.New("ERROR: 签名不匹配")
	}
	decode, err := base64.StdEncoding.DecodeString(secretMsg)
	if err != nil {
		return "", err
	}
	if len(decode) < aes.BlockSize {
		return "", errors.New("ERROR: 密文太短")
	}
	blockMode := cipher.NewCBCDecrypter(c.Block, c.BKey[:c.Block.BlockSize()])
	plantText := make([]byte, len(decode))
	blockMode.CryptBlocks(plantText, decode)
	plantText = pkCS7UnPadding(plantText)
	size := binary.BigEndian.Uint32(plantText[16:20])
	plantText = plantText[20:]
	corpID := plantText[size:]
	if string(corpID) != c.SuiteKey {
		return "", errors.New("ERROR: CorpID匹配不正确")
	}
	return string(plantText[:size]), nil
}

func (c *DingTalkCrypto) GetEncryptMsg(msg string) (map[string]string, error) {
	var timestamp = time.Now().Second()
	var nonce = randomString(12)
	str, sign, err := c.GetEncryptMsgDetail(msg, fmt.Sprint(timestamp), nonce)

	return map[string]string{"nonce": nonce, "timeStamp": fmt.Sprint(timestamp), "encrypt": str, "msg_signature": sign}, err
}
func (c *DingTalkCrypto) GetEncryptMsgDetail(msg, timestamp, nonce string) (string, string, error) {
	size := make([]byte, 4)
	binary.BigEndian.PutUint32(size, uint32(len(msg)))
	msg = randomString(16) + string(size) + msg + c.SuiteKey
	plantText := pkCS7Padding([]byte(msg), c.Block.BlockSize())
	if len(plantText)%aes.BlockSize != 0 {
		return "", "", errors.New("ERROR: 消息体size不为16的倍数")
	}
	blockMode := cipher.NewCBCEncrypter(c.Block, c.BKey[:c.Block.BlockSize()])
	chipherText := make([]byte, len(plantText))
	blockMode.CryptBlocks(chipherText, plantText)
	outMsg := base64.StdEncoding.EncodeToString(chipherText)
	signature := c.CreateSignature(c.Token, timestamp, nonce, string(outMsg))
	return string(outMsg), signature, nil
}

func sha1Sign(s string) string {
	// The pattern for generating a hash is `sha1.New()`,
	// `sha1.Write(bytes)`, then `sha1.Sum([]byte{})`.
	// Here we start with a new hash.
	h := sha1.New()

	// `Write` expects bytes. If you have a string `s`,
	// use `[]byte(s)` to coerce it to bytes.
	h.Write([]byte(s))

	// This gets the finalized hash result as a byte
	// slice. The argument to `Sum` can be used to append
	// to an existing byte slice: it usually isn't needed.
	bs := h.Sum(nil)

	// SHA1 values are often printed in hex, for example
	// in git commits. Use the `%x` format verb to convert
	// a hash results to a hex string.
	return fmt.Sprintf("%x", bs)
}

// 数据签名
func (c *DingTalkCrypto) CreateSignature(token, timestamp, nonce, msg string) string {
	params := make([]string, 0)
	params = append(params, token)
	params = append(params, timestamp)
	params = append(params, nonce)
	params = append(params, msg)
	sort.Strings(params)
	return sha1Sign(strings.Join(params, ""))
}

// 验证数据签名
func (c *DingTalkCrypto) VerificationSignature(token, timestamp, nonce, msg, sigture string) bool {
	return c.CreateSignature(token, timestamp, nonce, msg) == sigture
}

// 解密补位
func pkCS7UnPadding(plantText []byte) []byte {
	length := len(plantText)
	unpadding := int(plantText[length-1])
	return plantText[:(length - unpadding)]
}

// 加密补位
func pkCS7Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

// 随机字符串
func randomString(n int, alphabets ...byte) string {
	const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	var bytes = make([]byte, n)
	var randby bool
	if num, err := rand.Read(bytes); num != n || err != nil {
		r.Seed(time.Now().UnixNano())
		randby = true
	}
	for i, b := range bytes {
		if len(alphabets) == 0 {
			if randby {
				bytes[i] = alphanum[r.Intn(len(alphanum))]
			} else {
				bytes[i] = alphanum[b%byte(len(alphanum))]
			}
		} else {
			if randby {
				bytes[i] = alphabets[r.Intn(len(alphabets))]
			} else {
				bytes[i] = alphabets[b%byte(len(alphabets))]
			}
		}
	}
	return string(bytes)
}
